// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/image/image_skia.h"

#include <stddef.h>

#include "base/command_line.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/threading/simple_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/image/image_skia_source.h"
#include "ui/gfx/switches.h"

// Duplicated from base/threading/non_thread_safe.h so that we can be
// good citizens there and undef the macro.
#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON))
#define ENABLE_NON_THREAD_SAFE 1
#else
#define ENABLE_NON_THREAD_SAFE 0
#endif

namespace gfx {

namespace {

    class FixedSource : public ImageSkiaSource {
    public:
        explicit FixedSource(const ImageSkiaRep& image)
            : image_(image)
        {
        }

        ~FixedSource() override { }

        ImageSkiaRep GetImageForScale(float scale) override { return image_; }

    private:
        ImageSkiaRep image_;

        DISALLOW_COPY_AND_ASSIGN(FixedSource);
    };

    class FixedScaleSource : public ImageSkiaSource {
    public:
        explicit FixedScaleSource(const ImageSkiaRep& image)
            : image_(image)
        {
        }

        ~FixedScaleSource() override { }

        ImageSkiaRep GetImageForScale(float scale) override
        {
            if (!image_.unscaled() && image_.scale() != scale)
                return ImageSkiaRep();
            return image_;
        }

    private:
        ImageSkiaRep image_;

        DISALLOW_COPY_AND_ASSIGN(FixedScaleSource);
    };

    class DynamicSource : public ImageSkiaSource {
    public:
        explicit DynamicSource(const gfx::Size& size)
            : size_(size)
            , last_requested_scale_(0.0f)
        {
        }

        ~DynamicSource() override { }

        ImageSkiaRep GetImageForScale(float scale) override
        {
            last_requested_scale_ = scale;
            return gfx::ImageSkiaRep(size_, scale);
        }

        float GetLastRequestedScaleAndReset()
        {
            float result = last_requested_scale_;
            last_requested_scale_ = 0.0f;
            return result;
        }

    private:
        gfx::Size size_;
        float last_requested_scale_;

        DISALLOW_COPY_AND_ASSIGN(DynamicSource);
    };

    class NullSource : public ImageSkiaSource {
    public:
        NullSource()
        {
        }

        ~NullSource() override { }

        ImageSkiaRep GetImageForScale(float scale) override
        {
            return gfx::ImageSkiaRep();
        }

    private:
        DISALLOW_COPY_AND_ASSIGN(NullSource);
    };

} // namespace

namespace test {
    class TestOnThread : public base::SimpleThread {
    public:
        explicit TestOnThread(ImageSkia* image_skia)
            : SimpleThread("image_skia_on_thread")
            , image_skia_(image_skia)
            , can_read_(false)
            , can_modify_(false)
        {
        }

        void Run() override
        {
            can_read_ = image_skia_->CanRead();
            can_modify_ = image_skia_->CanModify();
            if (can_read_)
                image_skia_->image_reps();
        }

        void StartAndJoin()
        {
            Start();
            Join();
        }

        bool can_read() const { return can_read_; }

        bool can_modify() const { return can_modify_; }

    private:
        ImageSkia* image_skia_;

        bool can_read_;
        bool can_modify_;

        DISALLOW_COPY_AND_ASSIGN(TestOnThread);
    };

} // namespace test

class ImageSkiaTest : public testing::Test {
public:
    ImageSkiaTest()
    {
        // In the test, we assume that we support 1.0f and 2.0f DSFs.
        old_scales_ = ImageSkia::GetSupportedScales();

        // Sets the list of scale factors supported by resource bundle.
        std::vector<float> supported_scales;
        supported_scales.push_back(1.0f);
        supported_scales.push_back(2.0f);
        ImageSkia::SetSupportedScales(supported_scales);
    }
    ~ImageSkiaTest() override { ImageSkia::SetSupportedScales(old_scales_); }

private:
    std::vector<float> old_scales_;
    DISALLOW_COPY_AND_ASSIGN(ImageSkiaTest);
};

TEST_F(ImageSkiaTest, FixedSource)
{
    ImageSkiaRep image(Size(100, 200), 0.0f);
    ImageSkia image_skia(new FixedSource(image), Size(100, 200));
    EXPECT_EQ(0U, image_skia.image_reps().size());

    const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f);
    EXPECT_EQ(100, result_100p.GetWidth());
    EXPECT_EQ(200, result_100p.GetHeight());
    EXPECT_EQ(1.0f, result_100p.scale());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f);

    EXPECT_EQ(100, result_200p.GetWidth());
    EXPECT_EQ(200, result_200p.GetHeight());
    EXPECT_EQ(100, result_200p.pixel_width());
    EXPECT_EQ(200, result_200p.pixel_height());
    EXPECT_EQ(1.0f, result_200p.scale());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    const ImageSkiaRep& result_300p = image_skia.GetRepresentation(3.0f);

    EXPECT_EQ(100, result_300p.GetWidth());
    EXPECT_EQ(200, result_300p.GetHeight());
    EXPECT_EQ(100, result_300p.pixel_width());
    EXPECT_EQ(200, result_300p.pixel_height());
    EXPECT_EQ(1.0f, result_300p.scale());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    // Get the representation again and make sure it doesn't
    // generate new image skia rep.
    image_skia.GetRepresentation(1.0f);
    image_skia.GetRepresentation(2.0f);
    image_skia.GetRepresentation(3.0f);
    EXPECT_EQ(1U, image_skia.image_reps().size());
}

TEST_F(ImageSkiaTest, FixedScaledSource)
{
    ImageSkiaRep image(Size(100, 200), 1.0f);
    ImageSkia image_skia(new FixedScaleSource(image), Size(100, 200));
    EXPECT_EQ(0U, image_skia.image_reps().size());

    const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f);
    EXPECT_EQ(100, result_100p.GetWidth());
    EXPECT_EQ(200, result_100p.GetHeight());
    EXPECT_EQ(1.0f, result_100p.scale());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    // 2.0f data doesn't exist, then it falls back to 1.0f and rescale it.
    const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f);

    EXPECT_EQ(100, result_200p.GetWidth());
    EXPECT_EQ(200, result_200p.GetHeight());
    EXPECT_EQ(200, result_200p.pixel_width());
    EXPECT_EQ(400, result_200p.pixel_height());
    EXPECT_EQ(2.0f, result_200p.scale());
    EXPECT_EQ(2U, image_skia.image_reps().size());

    // Get the representation again and make sure it doesn't
    // generate new image skia rep.
    image_skia.GetRepresentation(1.0f);
    image_skia.GetRepresentation(2.0f);
    EXPECT_EQ(2U, image_skia.image_reps().size());
}

TEST_F(ImageSkiaTest, FixedUnscaledSource)
{
    ImageSkiaRep image(Size(100, 200), 0.0f);
    ImageSkia image_skia(new FixedScaleSource(image), Size(100, 200));
    EXPECT_EQ(0U, image_skia.image_reps().size());

    const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f);
    EXPECT_EQ(100, result_100p.pixel_width());
    EXPECT_EQ(200, result_100p.pixel_height());
    EXPECT_TRUE(result_100p.unscaled());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    // 2.0f data doesn't exist, but unscaled ImageSkiaRep shouldn't be rescaled.
    const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f);

    EXPECT_EQ(100, result_200p.pixel_width());
    EXPECT_EQ(200, result_200p.pixel_height());
    EXPECT_TRUE(result_200p.unscaled());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    // Get the representation again and make sure it doesn't
    // generate new image skia rep.
    image_skia.GetRepresentation(1.0f);
    image_skia.GetRepresentation(2.0f);
    EXPECT_EQ(1U, image_skia.image_reps().size());
}

TEST_F(ImageSkiaTest, DynamicSource)
{
    ImageSkia image_skia(new DynamicSource(Size(100, 200)), Size(100, 200));
    EXPECT_EQ(0U, image_skia.image_reps().size());
    const ImageSkiaRep& result_100p = image_skia.GetRepresentation(1.0f);
    EXPECT_EQ(100, result_100p.GetWidth());
    EXPECT_EQ(200, result_100p.GetHeight());
    EXPECT_EQ(1.0f, result_100p.scale());
    EXPECT_EQ(1U, image_skia.image_reps().size());

    const ImageSkiaRep& result_200p = image_skia.GetRepresentation(2.0f);
    EXPECT_EQ(100, result_200p.GetWidth());
    EXPECT_EQ(200, result_200p.GetHeight());
    EXPECT_EQ(200, result_200p.pixel_width());
    EXPECT_EQ(400, result_200p.pixel_height());
    EXPECT_EQ(2.0f, result_200p.scale());
    EXPECT_EQ(2U, image_skia.image_reps().size());

    // Get the representation again and make sure it doesn't
    // generate new image skia rep.
    image_skia.GetRepresentation(1.0f);
    EXPECT_EQ(2U, image_skia.image_reps().size());
    image_skia.GetRepresentation(2.0f);
    EXPECT_EQ(2U, image_skia.image_reps().size());
}

// Tests that image_reps returns all of the representations in the
// image when there are multiple representations for a scale factor.
// This currently is the case with ImageLoader::LoadImages.
TEST_F(ImageSkiaTest, ManyRepsPerScaleFactor)
{
    const int kSmallIcon1x = 16;
    const int kSmallIcon2x = 32;
    const int kLargeIcon1x = 32;

    ImageSkia image(new NullSource(), gfx::Size(kSmallIcon1x, kSmallIcon1x));
    // Simulate a source which loads images on a delay. Upon
    // GetImageForScaleFactor, it immediately returns null and starts loading
    // image reps slowly.
    image.GetRepresentation(1.0f);
    image.GetRepresentation(2.0f);

    // After a lengthy amount of simulated time, finally loaded image reps.
    image.AddRepresentation(ImageSkiaRep(
        gfx::Size(kSmallIcon1x, kSmallIcon1x), 1.0f));
    image.AddRepresentation(ImageSkiaRep(
        gfx::Size(kSmallIcon2x, kSmallIcon2x), 2.0f));
    image.AddRepresentation(ImageSkiaRep(
        gfx::Size(kLargeIcon1x, kLargeIcon1x), 1.0f));

    std::vector<ImageSkiaRep> image_reps = image.image_reps();
    EXPECT_EQ(3u, image_reps.size());

    int num_1x = 0;
    int num_2x = 0;
    for (size_t i = 0; i < image_reps.size(); ++i) {
        if (image_reps[i].scale() == 1.0f)
            num_1x++;
        else if (image_reps[i].scale() == 2.0f)
            num_2x++;
    }
    EXPECT_EQ(2, num_1x);
    EXPECT_EQ(1, num_2x);
}

TEST_F(ImageSkiaTest, GetBitmap)
{
    ImageSkia image_skia(new DynamicSource(Size(100, 200)), Size(100, 200));
    const SkBitmap* bitmap = image_skia.bitmap();
    EXPECT_NE(static_cast<SkBitmap*>(NULL), bitmap);
    EXPECT_FALSE(bitmap->isNull());
}

TEST_F(ImageSkiaTest, GetBitmapFromEmpty)
{
    // Create an image with 1 representation and remove it so the ImageSkiaStorage
    // is left with no representations.
    ImageSkia empty_image(ImageSkiaRep(Size(100, 200), 1.0f));
    ImageSkia empty_image_copy(empty_image);
    empty_image.RemoveRepresentation(1.0f);

    // Check that ImageSkia::bitmap() still returns a valid SkBitmap pointer for
    // the image and all its copies.
    const SkBitmap* bitmap = empty_image_copy.bitmap();
    ASSERT_NE(static_cast<SkBitmap*>(NULL), bitmap);
    EXPECT_TRUE(bitmap->isNull());
    EXPECT_TRUE(bitmap->empty());
}

TEST_F(ImageSkiaTest, BackedBySameObjectAs)
{
    // Null images should all be backed by the same object (NULL).
    ImageSkia image;
    ImageSkia unrelated;
    EXPECT_TRUE(image.BackedBySameObjectAs(unrelated));

    image.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10),
        1.0f));
    ImageSkia copy = image;
    copy.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10),
        2.0f));
    unrelated.AddRepresentation(gfx::ImageSkiaRep(gfx::Size(10, 10),
        1.0f));
    EXPECT_TRUE(image.BackedBySameObjectAs(copy));
    EXPECT_FALSE(image.BackedBySameObjectAs(unrelated));
    EXPECT_FALSE(copy.BackedBySameObjectAs(unrelated));
}

#if ENABLE_NON_THREAD_SAFE
TEST_F(ImageSkiaTest, EmptyOnThreadTest)
{
    ImageSkia empty;
    test::TestOnThread empty_on_thread(&empty);
    empty_on_thread.Start();
    empty_on_thread.Join();
    EXPECT_TRUE(empty_on_thread.can_read());
    EXPECT_TRUE(empty_on_thread.can_modify());
}

TEST_F(ImageSkiaTest, StaticOnThreadTest)
{
    ImageSkia image(ImageSkiaRep(Size(100, 200), 1.0f));
    EXPECT_FALSE(image.IsThreadSafe());

    test::TestOnThread image_on_thread(&image);
    // an image that was never accessed on this thread can be
    // read by other thread.
    image_on_thread.StartAndJoin();
    EXPECT_TRUE(image_on_thread.can_read());
    EXPECT_TRUE(image_on_thread.can_modify());
    EXPECT_FALSE(image.CanRead());
    EXPECT_FALSE(image.CanModify());

    image.DetachStorageFromThread();
    // An image is accessed by this thread,
    // so other thread cannot read/modify it.
    image.image_reps();
    test::TestOnThread image_on_thread2(&image);
    image_on_thread2.StartAndJoin();
    EXPECT_FALSE(image_on_thread2.can_read());
    EXPECT_FALSE(image_on_thread2.can_modify());
    EXPECT_TRUE(image.CanRead());
    EXPECT_TRUE(image.CanModify());

    image.DetachStorageFromThread();
    scoped_ptr<ImageSkia> deep_copy(image.DeepCopy());
    EXPECT_FALSE(deep_copy->IsThreadSafe());
    test::TestOnThread deepcopy_on_thread(deep_copy.get());
    deepcopy_on_thread.StartAndJoin();
    EXPECT_TRUE(deepcopy_on_thread.can_read());
    EXPECT_TRUE(deepcopy_on_thread.can_modify());
    EXPECT_FALSE(deep_copy->CanRead());
    EXPECT_FALSE(deep_copy->CanModify());

    scoped_ptr<ImageSkia> deep_copy2(image.DeepCopy());
    EXPECT_EQ(1U, deep_copy2->image_reps().size());
    // Access it from current thread so that it can't be
    // accessed from another thread.
    deep_copy2->image_reps();
    EXPECT_FALSE(deep_copy2->IsThreadSafe());
    test::TestOnThread deepcopy2_on_thread(deep_copy2.get());
    deepcopy2_on_thread.StartAndJoin();
    EXPECT_FALSE(deepcopy2_on_thread.can_read());
    EXPECT_FALSE(deepcopy2_on_thread.can_modify());
    EXPECT_TRUE(deep_copy2->CanRead());
    EXPECT_TRUE(deep_copy2->CanModify());

    image.DetachStorageFromThread();
    image.SetReadOnly();
    // A read-only ImageSkia with no source is thread safe.
    EXPECT_TRUE(image.IsThreadSafe());
    test::TestOnThread readonly_on_thread(&image);
    readonly_on_thread.StartAndJoin();
    EXPECT_TRUE(readonly_on_thread.can_read());
    EXPECT_FALSE(readonly_on_thread.can_modify());
    EXPECT_TRUE(image.CanRead());
    EXPECT_FALSE(image.CanModify());

    image.DetachStorageFromThread();
    image.MakeThreadSafe();
    EXPECT_TRUE(image.IsThreadSafe());
    test::TestOnThread threadsafe_on_thread(&image);
    threadsafe_on_thread.StartAndJoin();
    EXPECT_TRUE(threadsafe_on_thread.can_read());
    EXPECT_FALSE(threadsafe_on_thread.can_modify());
    EXPECT_TRUE(image.CanRead());
    EXPECT_FALSE(image.CanModify());
}

TEST_F(ImageSkiaTest, SourceOnThreadTest)
{
    ImageSkia image(new DynamicSource(Size(100, 200)), Size(100, 200));
    EXPECT_FALSE(image.IsThreadSafe());

    test::TestOnThread image_on_thread(&image);
    image_on_thread.StartAndJoin();
    // an image that was never accessed on this thread can be
    // read by other thread.
    EXPECT_TRUE(image_on_thread.can_read());
    EXPECT_TRUE(image_on_thread.can_modify());
    EXPECT_FALSE(image.CanRead());
    EXPECT_FALSE(image.CanModify());

    image.DetachStorageFromThread();
    // An image is accessed by this thread,
    // so other thread cannot read/modify it.
    image.image_reps();
    test::TestOnThread image_on_thread2(&image);
    image_on_thread2.StartAndJoin();
    EXPECT_FALSE(image_on_thread2.can_read());
    EXPECT_FALSE(image_on_thread2.can_modify());
    EXPECT_TRUE(image.CanRead());
    EXPECT_TRUE(image.CanModify());

    image.DetachStorageFromThread();
    image.SetReadOnly();
    EXPECT_FALSE(image.IsThreadSafe());
    test::TestOnThread readonly_on_thread(&image);
    readonly_on_thread.StartAndJoin();
    EXPECT_TRUE(readonly_on_thread.can_read());
    EXPECT_FALSE(readonly_on_thread.can_modify());
    EXPECT_FALSE(image.CanRead());
    EXPECT_FALSE(image.CanModify());

    image.DetachStorageFromThread();
    image.MakeThreadSafe();
    EXPECT_TRUE(image.IsThreadSafe());
    // Check if image reps are generated for supported scale factors.
    EXPECT_EQ(ImageSkia::GetSupportedScales().size(),
        image.image_reps().size());
    test::TestOnThread threadsafe_on_thread(&image);
    threadsafe_on_thread.StartAndJoin();
    EXPECT_TRUE(threadsafe_on_thread.can_read());
    EXPECT_FALSE(threadsafe_on_thread.can_modify());
    EXPECT_TRUE(image.CanRead());
    EXPECT_FALSE(image.CanModify());
}
#endif // ENABLE_NON_THREAD_SAFE

// Just in case we ever get lumped together with other compilation units.
#undef ENABLE_NON_THREAD_SAFE

TEST_F(ImageSkiaTest, Unscaled)
{
    SkBitmap bitmap;

    // An ImageSkia created with 1x bitmap is unscaled.
    ImageSkia image_skia = ImageSkia::CreateFrom1xBitmap(bitmap);
    EXPECT_TRUE(image_skia.GetRepresentation(1.0f).unscaled());
    ImageSkiaRep rep_2x(Size(100, 100), 2.0f);

    // When reps for other scales are added, the unscaled image
    // becomes scaled.
    image_skia.AddRepresentation(rep_2x);
    EXPECT_FALSE(image_skia.GetRepresentation(1.0f).unscaled());
    EXPECT_FALSE(image_skia.GetRepresentation(2.0f).unscaled());
}

namespace {

    std::vector<float> GetSortedScaleFactors(const gfx::ImageSkia& image)
    {
        const std::vector<ImageSkiaRep>& image_reps = image.image_reps();
        std::vector<float> scale_factors;
        for (size_t i = 0; i < image_reps.size(); ++i) {
            scale_factors.push_back(image_reps[i].scale());
        }
        std::sort(scale_factors.begin(), scale_factors.end());
        return scale_factors;
    }

} // namespace

TEST_F(ImageSkiaTest, ArbitraryScaleFactor)
{
    // source is owned by |image|
    DynamicSource* source = new DynamicSource(Size(100, 200));
    ImageSkia image(source, gfx::Size(100, 200));

    image.GetRepresentation(1.5f);
    EXPECT_EQ(2.0f, source->GetLastRequestedScaleAndReset());
    std::vector<ImageSkiaRep> image_reps = image.image_reps();
    EXPECT_EQ(2u, image_reps.size());

    std::vector<float> scale_factors = GetSortedScaleFactors(image);
    EXPECT_EQ(1.5f, scale_factors[0]);
    EXPECT_EQ(2.0f, scale_factors[1]);

    // Requesting 1.75 scale factor also falls back to 2.0f and rescale.
    // However, the image already has the 2.0f data, so it won't fetch again.
    image.GetRepresentation(1.75f);
    EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset());
    image_reps = image.image_reps();
    EXPECT_EQ(3u, image_reps.size());

    scale_factors = GetSortedScaleFactors(image);
    EXPECT_EQ(1.5f, scale_factors[0]);
    EXPECT_EQ(1.75f, scale_factors[1]);
    EXPECT_EQ(2.0f, scale_factors[2]);

    // Requesting 1.25 scale factor also falls back to 2.0f and rescale.
    // However, the image already has the 2.0f data, so it won't fetch again.
    image.GetRepresentation(1.25f);
    EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset());
    image_reps = image.image_reps();
    EXPECT_EQ(4u, image_reps.size());
    scale_factors = GetSortedScaleFactors(image);
    EXPECT_EQ(1.25f, scale_factors[0]);
    EXPECT_EQ(1.5f, scale_factors[1]);
    EXPECT_EQ(1.75f, scale_factors[2]);
    EXPECT_EQ(2.0f, scale_factors[3]);

    // 1.20 is falled back to 1.0.
    image.GetRepresentation(1.20f);
    EXPECT_EQ(1.0f, source->GetLastRequestedScaleAndReset());
    image_reps = image.image_reps();
    EXPECT_EQ(6u, image_reps.size());
    scale_factors = GetSortedScaleFactors(image);
    EXPECT_EQ(1.0f, scale_factors[0]);
    EXPECT_EQ(1.2f, scale_factors[1]);
    EXPECT_EQ(1.25f, scale_factors[2]);
    EXPECT_EQ(1.5f, scale_factors[3]);
    EXPECT_EQ(1.75f, scale_factors[4]);
    EXPECT_EQ(2.0f, scale_factors[5]);

    // Scale factor less than 1.0f will be falled back to 1.0f
    image.GetRepresentation(0.75f);
    EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset());
    image_reps = image.image_reps();
    EXPECT_EQ(7u, image_reps.size());

    scale_factors = GetSortedScaleFactors(image);
    EXPECT_EQ(0.75f, scale_factors[0]);
    EXPECT_EQ(1.0f, scale_factors[1]);
    EXPECT_EQ(1.2f, scale_factors[2]);
    EXPECT_EQ(1.25f, scale_factors[3]);
    EXPECT_EQ(1.5f, scale_factors[4]);
    EXPECT_EQ(1.75f, scale_factors[5]);
    EXPECT_EQ(2.0f, scale_factors[6]);

    // Scale factor greater than 2.0f is falled back to 2.0f because it's not
    // supported.
    image.GetRepresentation(3.0f);
    EXPECT_EQ(0.0f, source->GetLastRequestedScaleAndReset());
    image_reps = image.image_reps();
    EXPECT_EQ(8u, image_reps.size());
}

TEST_F(ImageSkiaTest, ArbitraryScaleFactorWithMissingResource)
{
    ImageSkia image(new FixedScaleSource(
                        ImageSkiaRep(Size(100, 200), 1.0f)),
        Size(100, 200));

    // Requesting 1.5f -- falls back to 2.0f, but couldn't find. It should
    // look up 1.0f and then rescale it. Note that the rescaled ImageSkiaRep will
    // have 2.0f scale.
    const ImageSkiaRep& rep = image.GetRepresentation(1.5f);
    EXPECT_EQ(1.5f, rep.scale());
    EXPECT_EQ(2U, image.image_reps().size());
    EXPECT_EQ(2.0f, image.image_reps()[0].scale());
    EXPECT_EQ(1.5f, image.image_reps()[1].scale());
}

TEST_F(ImageSkiaTest, UnscaledImageForArbitraryScaleFactor)
{
    // 0.0f means unscaled.
    ImageSkia image(new FixedScaleSource(
                        ImageSkiaRep(Size(100, 200), 0.0f)),
        Size(100, 200));

    // Requesting 2.0f, which should return 1.0f unscaled image.
    const ImageSkiaRep& rep = image.GetRepresentation(2.0f);
    EXPECT_EQ(1.0f, rep.scale());
    EXPECT_EQ("100x200", rep.pixel_size().ToString());
    EXPECT_TRUE(rep.unscaled());
    EXPECT_EQ(1U, image.image_reps().size());

    // Same for any other scale factors.
    const ImageSkiaRep& rep15 = image.GetRepresentation(1.5f);
    EXPECT_EQ(1.0f, rep15.scale());
    EXPECT_EQ("100x200", rep15.pixel_size().ToString());
    EXPECT_TRUE(rep15.unscaled());
    EXPECT_EQ(1U, image.image_reps().size());

    const ImageSkiaRep& rep12 = image.GetRepresentation(1.2f);
    EXPECT_EQ(1.0f, rep12.scale());
    EXPECT_EQ("100x200", rep12.pixel_size().ToString());
    EXPECT_TRUE(rep12.unscaled());
    EXPECT_EQ(1U, image.image_reps().size());
}

} // namespace gfx
